跳到主要内容

UART

本章介绍 Luckfox Lume 的串口设备、40Pin 接线、设备树配置,以及 UART3 PD14/PD15 引脚组与 Windows 主机通信和回环测试方法。

1. UART 简介​

UART(Universal Asynchronous Receiver‑Transmitter,通用异步收发器)是嵌入式领域广泛使用的串行通信协议。UART 采用异步传输机制,收发双方不需要共用时钟信号,数据以逐位串行的方式进行传输,非常适用于短距离、低速率外设之间的数据交互。

Luckfox Lume 使用 /dev/ttyASx 命名片内串口。本章使用 UART3:/dev/ttyAS3,115200波特率、8数据位、无校验、1停止位(8N1),关闭软、硬件流控。

/dev/ttyAS0 是系统控制台,不用于本章数据回环测试。

2. 串口测试(Shell)​

2.1 串口引脚图​

串口设备功能Lume 信号40Pin 物理引脚
/dev/ttyAS0系统控制台 TXPB9 / UART0_TX8
/dev/ttyAS0系统控制台 RXPB10 / UART0_RX10
/dev/ttyAS3通用串口 TXPD14 / UART3_TX15
/dev/ttyAS3通用串口 RXPD15 / UART3_RX13
公共地GNDGND6(或其他 GND 引脚)

2.2 设备树配置​

  1. 板级设备树路径:

    device/config/chips/t153/configs/luckfox_lume/linux-5.10-origin/board.dts
  2. SDK 中已定义 &pio UART3 引脚组,需要启用 &uart3:

    &pio {
    uart3_pins_active: uart3_pins@0 {
    pins = "PD14", "PD15";
    function = "uart3";
    };

    uart3_pins_sleep: uart3_pins@1 {
    pins = "PD14", "PD15";
    function = "io_disabled";
    };
    };

    &uart3 {
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart3_pins_active>;
    pinctrl-1 = <&uart3_pins_sleep>;
    status = "okay";
    };
  3. 编译设备树及打包镜像:

    ./build.sh dts
    ./build.sh
    ./build.sh pack

2.3 查看串口设备​

在 /dev 目录下,每一路 UART 串口对应一个独立的字符设备文件。设备命名一般以 ttyAS 搭配串口号生成,例如 /dev/ttyAS3 代表第 3 路 UART 串口。 可执行以下命令查看系统内所有串口设备:

root@luckfox:~# ls /dev/ttyAS*
/dev/ttyAS0 /dev/ttyAS3

2.4 设置串口​

  1. 用stty工具查询其通信参数:
    root@luckfox:~# stty -F /dev/ttyAS3
    speed 9600 baud; line = 0;
  2. 修改波特率,其中ispeed为输入速率,ospeed为输出速率:
    stty -F /dev/ttyAS3 ispeed 115200 ospeed 115200
  3. 关闭回显:
    stty -F /dev/ttyAS3 -echo

3.与Windows主机通讯​

  1. 将串口转接模块一端连接至电脑 USB 接口,另一端接线接入 LuckFox Lume 开发板,接线如下:

    Luckfox LumeUAB to UART
    第15脚 PD14RX
    第13脚 PD15TX
    第6脚 GNDGND
  2. 打开 MobaXterm 软件,新建串口 (Serial)会话;配置串口波特率,波特率为 115200,可根据实际修改后的串口参数进行调整。

  3. 在开发板上的终端执行以下指令,使用 echo 命令向终端设备文件写入字符串"Hello"和"world !":

    root@luckfox:~# echo Hello > /dev/ttyAS3
    root@luckfox:~# echo "world !" > /dev/ttyAS3
  4. Windows 上的串口调试助手会接收到内容:

4. 串口通信(Python程序)​

  1. 完整代码:


    #!/usr/bin/env python3
    import os
    import select
    import sys
    import termios
    import time

    DEVICE = "/dev/ttyAS3"
    MESSAGE = b"Luckfox Lume UART test\r\n"
    READ_SIZE = 128
    TIMEOUT = 3.0


    def configure_uart(fd):
    config = termios.tcgetattr(fd)
    config[0] = 0
    config[1] = 0
    config[2] &= ~(termios.CSIZE | termios.PARENB |
    termios.PARODD | termios.CSTOPB |
    getattr(termios, "CRTSCTS", 0))
    config[2] |= termios.CS8 | termios.CLOCAL | termios.CREAD
    config[3] = 0
    config[4] = config[5] = termios.B115200
    config[6][termios.VMIN] = 0
    config[6][termios.VTIME] = 0
    termios.tcsetattr(fd, termios.TCSANOW, config)


    def write_all(fd, data, deadline):
    sent = 0
    while sent < len(data):
    remaining = deadline - time.monotonic()
    if remaining <= 0 or not select.select([], [fd], [], remaining)[1]:
    raise TimeoutError("UART transmit timeout")
    try:
    count = os.write(fd, data[sent:])
    except BlockingIOError:
    continue
    if count == 0:
    raise OSError("UART write returned zero")
    sent += count


    def read_reply(fd, deadline):
    received = bytearray()

    while len(received) < READ_SIZE:
    remaining = deadline - time.monotonic()
    if remaining <= 0:
    break
    if not select.select([fd], [], [], remaining)[0]:
    break
    try:
    chunk = os.read(fd, READ_SIZE - len(received))
    except BlockingIOError:
    continue
    if not chunk:
    break
    received.extend(chunk)

    return bytes(received)


    def communicate(device=DEVICE):
    fd = os.open(device, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
    saved = None
    try:
    saved = termios.tcgetattr(fd)
    configure_uart(fd)
    termios.tcflush(fd, termios.TCIFLUSH)

    deadline = time.monotonic() + TIMEOUT
    write_all(fd, MESSAGE, deadline)
    termios.tcdrain(fd)
    print("TX:", MESSAGE.decode("utf-8").rstrip("\r\n"))

    reply = read_reply(fd, deadline)
    if not reply:
    print("RX: (no data)")
    print("Read 0 bytes")
    return 0
    text = reply.decode("utf-8", errors="replace")
    print(f"RX: {text}")
    print(f"Read {len(reply)} bytes")
    return 0
    finally:
    try:
    if saved is not None:
    termios.tcsetattr(fd, termios.TCSANOW, saved)
    finally:
    os.close(fd)


    def main():
    try:
    return communicate()
    except KeyboardInterrupt:
    print("UART communication interrupted.", file=sys.stderr)
    return 130
    except (OSError, termios.error) as error:
    print(f"UART error: {error}", file=sys.stderr)
    return 1


    if __name__ == "__main__":
    sys.exit(main())
  2. 打开并配置串口:

    fd = os.open(device, os.O_RDWR | os.O_NOCTTY | os.O_NONBLOCK)
    saved = termios.tcgetattr(fd)
    configure_uart(fd)

    以读写、非阻塞方式打开 /dev/ttyAS3,配置115200、8N1、无软硬件流控,并关闭回显和字符转换。

  3. 发送数据:

    write_all(fd, MESSAGE, deadline)
    termios.tcdrain(fd)

    write_all() 循环处理部分写入,tcdrain() 等待发送队列中的数据传输完成。

  4. 接收数据:

    reply = read_reply(fd, deadline)

    发送完成后等待 Windows 回复,达到128字节或总计3秒超时后返回。

  5. 执行程序:

    python3 UART.py

    执行结果:

5. 串口通信(C程序)​

  1. 完整代码:

    #define _DEFAULT_SOURCE
    #define _POSIX_C_SOURCE 200809L
    #include <errno.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <sys/select.h>
    #include <termios.h>
    #include <time.h>
    #include <unistd.h>

    #define DEVICE "/dev/ttyAS3"
    #define READ_SIZE 128
    #define TIMEOUT 3.0

    static const unsigned char message[] = "Luckfox Lume UART test\r\n";

    static double monotonic_seconds(void)
    {
    struct timespec now;

    if (clock_gettime(CLOCK_MONOTONIC, &now) < 0)
    return -1.0;
    return (double)now.tv_sec + (double)now.tv_nsec / 1000000000.0;
    }

    static int wait_ready(int fd, int writing, double deadline)
    {
    for (;;) {
    double now = monotonic_seconds();
    double remaining;
    struct timeval timeout;
    fd_set set;
    int result;

    if (now < 0)
    return -1;
    remaining = deadline - now;
    if (remaining <= 0)
    return 0;

    timeout.tv_sec = (time_t)remaining;
    timeout.tv_usec =
    (suseconds_t)((remaining - timeout.tv_sec) * 1000000.0);
    FD_ZERO(&set);
    FD_SET(fd, &set);

    result = select(fd + 1, writing ? NULL : &set,
    writing ? &set : NULL, NULL, &timeout);
    if (result < 0 && errno == EINTR)
    continue;
    return result;
    }
    }

    static int configure_uart(int fd, struct termios *saved)
    {
    struct termios config;

    if (tcgetattr(fd, saved) < 0)
    return -1;
    config = *saved;
    config.c_iflag = 0;
    config.c_oflag = 0;
    config.c_cflag &= ~(CSIZE | PARENB | PARODD | CSTOPB);
    #ifdef CRTSCTS
    config.c_cflag &= ~CRTSCTS;
    #endif
    config.c_cflag |= CS8 | CLOCAL | CREAD;
    config.c_lflag = 0;
    config.c_cc[VMIN] = 0;
    config.c_cc[VTIME] = 0;

    if (cfsetispeed(&config, B115200) < 0 ||
    cfsetospeed(&config, B115200) < 0 ||
    tcsetattr(fd, TCSANOW, &config) < 0)
    return -1;
    return 0;
    }

    static int write_all(int fd, const unsigned char *data, size_t size,
    double deadline)
    {
    size_t sent = 0;

    while (sent < size) {
    int ready = wait_ready(fd, 1, deadline);
    ssize_t count;

    if (ready <= 0) {
    if (ready == 0)
    errno = ETIMEDOUT;
    return -1;
    }
    count = write(fd, data + sent, size - sent);
    if (count < 0 && (errno == EAGAIN || errno == EINTR))
    continue;
    if (count <= 0) {
    if (count == 0)
    errno = EIO;
    return -1;
    }
    sent += (size_t)count;
    }
    return 0;
    }

    static ssize_t read_reply(int fd, unsigned char *data, size_t size,
    double deadline)
    {
    size_t used = 0;

    while (used < size) {
    int ready = wait_ready(fd, 0, deadline);
    ssize_t count;

    if (ready < 0)
    return -1;
    if (ready == 0)
    break;

    count = read(fd, data + used, size - used);
    if (count < 0 && (errno == EAGAIN || errno == EINTR))
    continue;
    if (count < 0)
    return -1;
    if (count == 0)
    break;
    used += (size_t)count;
    }
    return (ssize_t)used;
    }

    static int communicate(const char *device)
    {
    int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);
    int result = 1;
    int have_saved = 0;
    struct termios saved;
    unsigned char received[READ_SIZE];
    const size_t message_size = sizeof(message) - 1;
    double now;
    double deadline;
    ssize_t received_size;

    if (fd < 0) {
    perror(device);
    return 1;
    }
    if (fd >= FD_SETSIZE) {
    fprintf(stderr, "UART file descriptor exceeds select limit\n");
    goto cleanup;
    }
    if (configure_uart(fd, &saved) < 0)
    goto error;
    have_saved = 1;
    if (tcflush(fd, TCIFLUSH) < 0)
    goto error;

    now = monotonic_seconds();
    if (now < 0)
    goto error;
    deadline = now + TIMEOUT;

    if (write_all(fd, message, message_size, deadline) < 0)
    goto error;
    if (tcdrain(fd) < 0)
    goto error;

    printf("TX: %.*s\n", (int)(message_size - 2), message);
    received_size = read_reply(fd, received, sizeof(received), deadline);
    if (received_size < 0)
    goto error;
    if (received_size == 0) {
    puts("RX: (no data)");
    puts("Read 0 bytes");
    result = 0;
    goto cleanup;
    }

    printf("RX: ");
    fwrite(received, 1, (size_t)received_size, stdout);
    putchar('\n');
    printf("Read %zd bytes\n", received_size);
    result = 0;
    goto cleanup;

    error:
    perror("UART");
    cleanup:
    if (have_saved && tcsetattr(fd, TCSANOW, &saved) < 0) {
    perror("UART restore");
    result = 1;
    }
    if (close(fd) < 0) {
    perror("UART close");
    result = 1;
    }
    return result;
    }

    int main(void)
    {
    return communicate(DEVICE);
    }
  2. 打开并配置串口:

    int fd = open(device, O_RDWR | O_NOCTTY | O_NONBLOCK);

    config.c_cflag &= ~(CSIZE | PARENB | PARODD | CSTOPB);
    config.c_cflag |= CS8 | CLOCAL | CREAD;
    cfsetispeed(&config, B115200);
    cfsetospeed(&config, B115200);

    以非阻塞方式打开 /dev/ttyAS3,设置为115200、8位数据位、无校验、1位停止位,并关闭软硬件流控。发送前清空接收缓冲区,结束时恢复串口原配置。

  3. 发送与接收数据:

    if (write_all(fd, message, message_size, deadline) < 0)
    goto error;
    if (tcdrain(fd) < 0)
    goto error;

    received_size = read_reply(fd, received, sizeof(received), deadline);

    串口发送 Luckfox Lume UART test\r\n,然后在同一个3秒截止时间内接收最多128字节,并直接显示收到的内容和字节数。

  4. 交叉编译:使用 Lume SDK 的 ARM 工具链。

    export PATH=<Luckfox_Lume_SDK>/out/toolchain/gcc-linaro-11.3.1-2022.06-x86_64_arm-linux-gnueabihf/bin:$PATH
    arm-linux-gnueabihf-gcc -std=c11 -O2 -Wall -Wextra UART.c -o UART
  5. 运行程序:

    chmod +x UART
    ./UART

    执行结果:

    超时内没有收到数据时: